﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Migrator.Entities;
using Migrator.Services.Advertisement.Packets;
using Migrator.Packets;
using Migrator.Services.MobileAgents;
using Migrator.Util;

namespace Migrator.Services.Advertisement
{
    /// <summary>
    /// Service responsible for sending MobileIP advertisement packets
    /// </summary>
    public class Advertiser : IService
    {
        #region Constructor
        public Advertiser(Node client)
        {
            this.Client = client;
        } 
        #endregion

        #region Public methods

        public void Update()
        {
            // Don't advertise unless the node is a mobility agent
            if (this.homeAgent.Enabled || this.foreignAgent.Enabled)
            {
                // Is it time to send an advertisement?
                if (this.timeLeft == 0)
                {
                    this.CreateAndSendAdvertisement();
                }
                else
                {
                    this.timeLeft--;
                }
            }
        }

        public bool Filter(IPPacket packet)
        {
            // Don't advertise unless the node is a mobility agent
            if (!this.homeAgent.Enabled && !this.foreignAgent.Enabled)
                return false;

            return packet is ICMPRouterSolicitationPacket;
        }

        public void Handle(IPPacket packet)
        {
            if (packet is ICMPRouterSolicitationPacket)
                this.Handle((ICMPRouterSolicitationPacket)packet);
        }

        /// <summary>
        /// Advertiser configuration
        /// </summary>
        /// <param name="configuration">
        /// [0] client's home agent service
        /// [1] client's foreign agent service
        /// </param>
        /// <returns>Itself</returns>
        public IService Configure(params object[] configuration)
        {
            this.homeAgent = (HomeAgent) configuration[0];
            this.foreignAgent = (ForeignAgent) configuration[1];
            this.timeLeft = 0;

            this.Enabled = true;
            return this;
        }

        #endregion

        #region Public properties
        public bool Enabled
        {
            get;
            set;
        }

        public Node Client
        {
            get;
            protected set;
        }

        #endregion

        #region Internal methods
        /// <summary>
        /// Creates and sends an advertisement packet
        /// </summary>
        private void CreateAndSendAdvertisement()
        {
            // First, we initialize the packet
            MobileIPAdvertisementPacket packet = this.InitializePacket();

            // Then we send it 
            this.Client.Send(packet);

            // Determines when will the next advertisement occur.
            Random random = new Random();
            timeLeft = (int)random.Next(MinIntervalBetweenAds, MaxIntervalBetweenAds);
        }

        /// <summary>
        /// Initialize the advertisement packet
        /// </summary>
        /// <returns>The packet initialized</returns>
        private MobileIPAdvertisementPacket InitializePacket()
        {
            MobileIPAdvertisementPacket advertisementPacket = new MobileIPAdvertisementPacket(this.Client);

            /*  
             * RFC 3344, section 2.1 states that:
             * 
             * «If sent periodically, the nominal interval at which Agent
             * Advertisements are sent SHOULD be no longer than 1/3 of the
             * advertisement Lifetime given in the ICMP header.»
             */
            advertisementPacket.LifeTime = MaxIntervalBetweenAds * 3;
            advertisementPacket.SequenceNumber = this.sequenceNumber;
            advertisementPacket.RegistrationLifeTime = this.foreignAgent.MaxRegistrationLifetime;

            advertisementPacket.H = homeAgent.Enabled;
            advertisementPacket.F = foreignAgent.Enabled;

            advertisementPacket.RouterAddresses.Add(this.Client.IPAddress);

            // A foreign-agent care-of address is an IP address of the foreign agent
            advertisementPacket.CareOfAddresses.Add(this.Client.IPAddress);

            advertisementPacket.B = false;
            advertisementPacket.R = true;
            advertisementPacket.T = false;

            advertisementPacket.Code = 0;

            // Finally, we increment the sequence number
            this.IncrementSequenceNumber();

            return advertisementPacket;
        }

        /// <summary>
        /// Increments the sequence number for the next packet
        /// </summary>
        /// <remarks>
        /// Transcript from RFC 3344:
        /// 
        /// 2.3.2. Sequence Numbers and Rollover Handling
        ///
        /// The sequence number in Agent Advertisements ranges from 0 to 0xffff.
        /// After booting, an agent MUST use the number 0 for its first
        /// advertisement.  Each subsequent advertisement MUST use the sequence
        /// number one greater, with the exception that the sequence number
        /// 0xffff MUST be followed by sequence number 256.  In this way, mobile
        /// nodes can distinguish a reduction in the sequence number that occurs
        /// after a reboot from a reduction that results in rollover of the
        /// sequence number after it attains the value 0xffff.
        /// </remarks>
        private void IncrementSequenceNumber()
        {
            if (sequenceNumber == 0xffff)
                sequenceNumber = 256;
            else
                sequenceNumber++;
        }

        private void Handle(ICMPRouterSolicitationPacket packet)
        {
            this.CreateAndSendAdvertisement();
        }
        #endregion

        #region Internal data

        /// <summary>
        /// The client's HomeAgent service
        /// </summary>
        private HomeAgent homeAgent;

        /// <summary>
        /// The client's ForeignAgent service
        /// </summary>
        private ForeignAgent foreignAgent;

        /// <summary>
        /// Sequence number for the next packet
        /// </summary>
        private ushort sequenceNumber = 0;
        
        /// <summary>
        /// Time left until the next advertisement
        /// </summary>
        private int timeLeft = 0;

        #endregion

        #region Simulation parameters

        /// <summary>
        /// Maximum interval between two successive advertisements
        /// </summary>
        private const int MaxIntervalBetweenAds = 50;

        /// <summary>
        /// Minimum interval between two successive advertisements
        /// </summary>
        private const int MinIntervalBetweenAds = 10;

        #endregion
    }
}

